/**
 * Created by cleverdou on 2018/7/27.
 */
'use strict';

const {messageSend, currentMockEnvironment, mockDataDic, onlineSocketObj} = require("../mockSocket");

const express = require('express');
const router = express.Router();
const proxy = require('http-proxy-middleware');
const Router = require('http-proxy-middleware/lib/router');
const PathRewriter = require('http-proxy-middleware/lib/path-rewriter');
const pako = require('pako');
const queryString = require('querystring');
const {parseJSON, checkNullObj} = require('../utils');
const envConfig = require('../env');
const _ = require('lodash');
const url = require('url');


const redirectRegex = /^201|30(1|2|7|8)$/;


function insertScript(str, scriptElm) {
    let index;
    if ((index = str.indexOf('<script>')) > -1) {
        str = str.slice(0, index) + scriptElm + str.slice(index);
    } else if (str.indexOf('</body>') > -1) {
        str = str.replace('</body>', scriptElm + '</body>');
    } else if (str.indexOf('</html>') > -1) {
        str = str.replace('</html>', scriptElm + '</html>');
    } else {
        str = str + scriptElm;
    }
    return str;
}


function proxyHandle(req, res, next) {

    const socketId = req.params.socketId;
    const option = getProxyOptions(socketId, res);
    if (!option) {
        return;
    }

    let reqMockHostAndRouter = option.hostRewrite = req.headers.host + req.baseUrl;
    let routerKey = '/';
    if (req.path && req.path.startsWith('/') && req.path.length > 1) {
        routerKey += req.path.split('/')[1]
    }
    reqMockHostAndRouter += routerKey;
    let newTarget = (Router.getTarget(req, option) || option.target);
    let newPath = PathRewriter.create(option.pathRewrite)(req.url, req);
    newTarget = newTarget.endsWith('/') ? newTarget.substr(0, newTarget.length - 1) : newTarget;
    newPath = newPath.startsWith('/') ? newPath : '/' + newPath;
    const originalUrl = newTarget + newPath;
    req.url_org = req.url;
    /**
     * 返回mock数据
     * */
    if (onlineSocketObj[socketId] !== undefined) {
        if (mockDataDic[socketId] !== undefined &&
            mockDataDic[socketId][req.path] !== undefined) {
            res.send(mockDataDic[socketId][req.path]['data']);
            let requestData = {};
            if (!checkNullObj(req.body) && !checkNullObj(req.query)) {
                requestData.body = req.body;
                requestData.query = req.query;
            }
            else if (!checkNullObj(req.query)) {
                requestData = req.query
            } else if (!checkNullObj(req.body)) {
                requestData = req.body
            }
            let obj = {
                method: req.method,
                url: originalUrl,
                data: JSON.stringify(requestData),
                headers: req.headers,
                urlIdentification: req.path,
                timestamp: (new Date()).valueOf(),
            };

            messageSend(socketId, {
                config: obj,
                data: mockDataDic[socketId][req.path]['data'],
                status: 200,
                isMock: true,
                mockTag: mockDataDic[socketId][req.path]['mockTag']
            });
            return;
        }
    }

    option.onError = function (err, req, res) {
        res.writeHead(500, {
            'Content-Type': 'text/plain'
        });
        res.end('Something went wrong. And we are reporting a custom error message.');
    };
    option.onProxyRes = function (proxyRes, req, res) {
        setRedirectHostRewrite(req, res, proxyRes, option);
        const ishtml = isHtml(proxyRes, req);
        const socketId = req.params.socketId;
        const host = req.headers.host;
        const contentEncoding = proxyRes.headers && !!proxyRes.headers['content-encoding'];
        if (ishtml) {
            delete proxyRes.headers['content-encoding'];
            delete proxyRes.headers['content-length'];
        }

        let body = new Buffer('');
        res.write = function (data) {
            body = Buffer.concat([body, data]);
        };

        proxyRes.on('end', function () {
            let data;
            let requestData = {};
            if (!checkNullObj(req.body) && !checkNullObj(req.query)) {
                requestData.body = req.body;
                requestData.query = req.query;
            }
            else if (!checkNullObj(req.query)) {
                requestData = req.query
            } else if (!checkNullObj(req.body)) {
                requestData = req.body
            }
            const config = {
                method: req.method,
                url: originalUrl,
                data: JSON.stringify(requestData),
                headers: req.headers,
                urlIdentification: req.path,
                timestamp: (new Date()).valueOf(),
            };
            if (contentEncoding) {
                data = pako.inflate(body, {to: 'string'});
            } else {
                data = body.toString();
            }
            if (proxyRes.statusCode >= 200 && proxyRes.statusCode < 300) {
                messageSend(socketId, {
                    config,
                    data: parseJSON(data),
                    status: proxyRes.statusCode,
                    isMock: false,
                    mockTag: -1
                });
            } else {
                messageSend(socketId, {
                    config,
                    data: {
                        state: proxyRes.statusCode,
                        statusText: proxyRes.statusMessage,
                        Error: parseJSON(data)
                    },
                    status: proxyRes.statusCode,
                    isMock: false,
                    mockTag: -1
                });
            }
            ishtml ? res.end(insertScript(data, createScript(host, socketId, reqMockHostAndRouter, {
                ...option.router,
                '/': option.target
            }))) : res.end(body);


        });
    };
    option.onProxyReq = function (proxyReq, req, res) {
        const host = url.parse(newTarget).host;
        if (/stb\d/.test(newTarget)) {
            proxyReq.setHeader('Host', host.replace(/stb\d/, 'stb'));
        }
        if (req.body) {
            if (!req.body || !Object.keys(req.body).length) {
                return;
            }
            let contentType = proxyReq.getHeader('Content-Type');
            let bodyData;

            if (contentType.includes('application/json')) {
                bodyData = JSON.stringify(req.body);
            }

            if (contentType.includes('application/x-www-form-urlencoded')) {
                bodyData = queryString.stringify(req.body);
            }

            if (bodyData) {
                proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData));
                proxyReq.write(bodyData);
            }
        }
    };
    const apiProxy = proxy(option);
    apiProxy(...arguments);
};


/**
 * 获取代理配置
 */

function getProxyOptions(socketId, res) {

    if (onlineSocketObj[socketId] === undefined) {
        res.send({
            "errorMessage": "socket不存在",
            "errorCode": "0000001",
            "succeed": false,
            "data": null
        });
        return;
    }

    const env = currentMockEnvironment[socketId];

    if (!env) {
        res.send({
            "errorMessage": "通话已失效，请重新扫描开始mock",
            "errorCode": "0000001",
            "succeed": false,
            "data": null
        });
        return;
    }


    const business = envConfig[env.business];

    if (!business) {
        res.send({
            "errorMessage": "业务线不存在",
            "errorCode": "0000001",
            "succeed": false,
            "data": null
        });
        return;
    }
    let router = {...business.routers[env.env]};
    const target = router['/'];
    delete router['/'];
    return {
        target,
        changeOrigin: true,
        secure: false,
        autoRewrite: true,
        protocolRewrite: 'http',
        pathRewrite: function (path, req) {
            const routerKeys = Object.keys(router);
            path = path.replace(req.baseUrl, '');
            const found = routerKeys.find((routerPath) => {
                return path.startsWith(routerPath)
            });
            if (found) {
                return path.replace(found, '');
            }
            return path;
        },
        onError(err, req, res) {
            console.log('err', err);
        },
        router
    };
}


function setRedirectHostRewrite(req, res, proxyRes, options) {
    if ((options.hostRewrite || options.autoRewrite || options.protocolRewrite)
        && proxyRes.headers['location']
        && redirectRegex.test(proxyRes.statusCode)) {
        let _o_url = req.url;
        try {
            req.url = req.url_org;
            let newTarget = Router.getTarget(req, options);
            let target;
            if (/stb\d/.test(newTarget)) {
                target = url.parse(newTarget.replace(/stb\d/, 'stb'));
            } else {
                target = url.parse(newTarget);
            }

            const u = url.parse(proxyRes.headers['location']);


            // make sure the redirected host matches the target host before rewriting
            if (target.host != u.host) {
                return;
            }

            if (options.hostRewrite) {
                u.host = options.hostRewrite;
            } else if (options.autoRewrite) {
                u.host = req.headers['host'];
            }
            if (options.protocolRewrite) {
                u.protocol = options.protocolRewrite;
            }
            if (options.router) {
                const routerKeys = Object.keys(options.router);
                let path = req.path;
                const found = routerKeys.find((routerPath) => {
                    return path.startsWith(routerPath)
                });
                if (found) {
                    u.host += found;
                }
            }
            proxyRes.headers['location'] = u.format();
        } finally {
            req.url = _o_url;
        }
    }
}

function isHtml(proxyRes, req) {
    return (proxyRes.headers &&
        proxyRes.headers['content-type'] &&
        proxyRes.headers['content-type'].match('text/html') &&
        req.method === 'GET'
    );
}


function createScript(host, socketID, reqMockHostAndRouter, router) {
    let routerJSON = Object.entries(router).reduce(function (router, currentRouter) {
        router[url.parse(currentRouter[1].replace(/(.stb\d*)|(.sit)|(.pre)/, '')).hostname] = currentRouter[0];
        return router;
    }, {});
    routerJSON = JSON.stringify(routerJSON);
    return `<script>
    var mockHost = "http://${host}/${socketID}";
    var originalXHROpen = XMLHttpRequest.prototype.open;
    var router = ${routerJSON};
    XMLHttpRequest.prototype.open = function (method, url) {
        if (typeof url === 'string' && url ) {
             var _url;
            try {
              _url  = new URL(url.replace(/(.stb\\d*)|(.sit)|(.pre)/, ''));
            }catch (e) {
                      return originalXHROpen.apply(this, arguments);
            }
            if(url.indexOf(location.host)>=0){
                arguments[1]=url.replace("http://"+location.host,mockHost);
            }else if (_url.hostname in router) {
                var regex =
                    "^((http|ftp|https)://)([0-9a-z_!~*]+\\\\.)*([0-9a-z][0-9a-z-]{0,61})?[0-9a-z]\\\\.[a-zA-Z]{2,6}";
                arguments[1] = url.replace(new RegExp(regex), router[_url.hostname]==='/'?mockHost:mockHost+router[_url.hostname]);
            }
        }
        return originalXHROpen.apply(this, arguments);
    };
 </script>`;
}


module.exports = proxyHandle;
